package com.ziroom.crm.mq.core.retry.delay;

import com.ziroom.crm.mq.core.retry.*;
import lombok.extern.slf4j.Slf4j;

/**
 * 指数延时，默认最大延时1天，内存延时的时候慎重
 * <p/>
 *
 * @author WeiShaoying
 * @date Created in 2020年09月17日 10:45
 * @since 3.0.0
 */
@Slf4j
@SuppressWarnings("ALL")
public class ExponentialDelayPolicy implements DelayPolicy {

    /**
     * The default 'initialInterval' value - 100 millisecs. Coupled with the default
     * 'multiplier' value this gives a useful initial spread of pauses for 1-5 retries.
     */
    public static final long DEFAULT_INITIAL_INTERVAL = 100L;

    /**
     * The default maximum delay time (1 min).
     */
    public static final long DEFAULT_MAX_INTERVAL = 60 * 1000L;

    /**
     * The default 'multiplier' value - value 2 (100% increase per delay).
     */
    public static final double DEFAULT_MULTIPLIER = 2;

    /**
     * The initial delay interval.
     * 100
     */
    private volatile long initialInterval = DEFAULT_INITIAL_INTERVAL;

    /**
     * The maximum value of the delay period in milliseconds.
     */
    private volatile long maxInterval = DEFAULT_MAX_INTERVAL;

    /**
     * The value to add to the delay period for each retry attempt.
     */
    private volatile double multiplier = DEFAULT_MULTIPLIER;

    private final Sleeper sleeper = new ThreadSleeper();

    public void setInitialInterval(long initialInterval) {
        this.initialInterval = (initialInterval > 1 ? initialInterval : 1);
    }

    public void setMultiplier(double multiplier) {
        this.multiplier = (multiplier > 1.0 ? multiplier : 1.0);
    }

    public void setMaxInterval(long maxInterval) {
        this.maxInterval = maxInterval > 0 ? maxInterval : 1;
    }

    public long getInitialInterval() {
        return this.initialInterval;
    }

    public long getMaxInterval() {
        return this.maxInterval;
    }

    public double getMultiplier() {
        return this.multiplier;
    }


    /**
     * 开始执行
     *
     * @param retryContext
     * @return
     */
    @Override
    public DelayContext start(RetryContext retryContext) {
        return new ExponentialDelayContext(this.initialInterval, this.multiplier, this.maxInterval);
    }

    /**
     * 执行延迟,延迟消息执行
     *
     * @param delayContext
     */
    @Override
    public void delay(DelayContext delayContext) {
        ExponentialDelayContext context = (ExponentialDelayContext) delayContext;
        try {
            long sleepTime = context.getSleepAndIncrement();
            if (log.isDebugEnabled()) {
                log.info("==Exponential Sleep {} ", sleepTime);
            }
            this.sleeper.sleep(sleepTime);
        } catch (InterruptedException e) {
            log.error("ExponentialDelayPolicy InterruptedException", e);
        }
    }

    /**
     * 当前delay 策略耗时
     *
     * @return 毫秒
     */
    @Override
    public long getDelayTime(DelayContext delayContext) {
        ExponentialDelayContext context = (ExponentialDelayContext) delayContext;
        return context.getSleepAndIncrement();
    }


    public static class ExponentialDelayContext implements DelayContext {

        private static final long serialVersionUID = 2783611094331011184L;

        private final double multiplier;

        private final long maxInterval;

        private long interval;


        public ExponentialDelayContext(long interval, double multiplier, long maxInterval) {
            this.interval = interval;
            this.multiplier = multiplier;
            this.maxInterval = maxInterval;
        }

        public synchronized long getSleepAndIncrement() {
            long sleep = this.interval;
            if (sleep > this.maxInterval) {
                sleep = this.maxInterval;
            } else {
                this.interval = getNextInterval();
            }
            return sleep;
        }

        protected long getNextInterval() {
            return (long) (this.interval * this.multiplier);
        }

        public double getMultiplier() {
            return this.multiplier;
        }

        public long getInterval() {
            return this.interval;
        }

        public long getMaxInterval() {
            return this.maxInterval;
        }
    }

}
